/*
 * BlackLightning.Timekeeper.cs
 * Andy Tidball
 * 
 * Project: Black Lightning
 * Copyright: GNU General Public License
 */

using System;
using System.Threading;
using BlackLightning.PluginLib;
using BLLib.Collections.Generic;

namespace BlackLightning {
	/// <summary>
	/// A static class for keeping track of time and timers.
	/// </summary>
	internal class Timekeeper : IDisposable {
		// static data
		private static DateTime _OverallStartTime; // the time that the class was created

		/// <summary>
		/// Creates the Timekeeper class.
		/// </summary>
		static Timekeeper() {
			_OverallStartTime = DateTime.Now;
		}

		/// <summary>
		/// Gets the date and time that the Timekeeper class was created.
		/// </summary>
		public DateTime OverallStartTime {
			get {
				return _OverallStartTime;
			}
		}

		/// <summary>
		/// Gets the amount of time that the Timekeeper class has been running.
		/// </summary>
		public TimeSpan OverallRunningTime {
			get {
				return DateTime.Now - _OverallStartTime;
			}
		}

		/// <summary>
		/// Gets a count in milliseconds of how long the Timekeeper class has been running.
		/// </summary>
		public int OverallTickCount {
			get {
				return OverallRunningTime.Milliseconds;
			}
		}

		// member data
		private DateTime          _StartTime;       // the time that the timekeeper was started
		private SortedList<Timer> _Timers;          // a list of timers being tracked
		private Thread            _UpdateThread;    // a thread that updates the tracked timers
		private bool              _UpdateRunning;   // whether or not to keep the update thread running
		private int               _TimerResolution; // the number of milliseconds between timer updates

		/// <summary>
		/// Constructs a new Timekeeper.
		/// </summary>
		public Timekeeper()
			: this(100) {
		}

		/// <summary>
		/// Constructs a new Timekeeper.
		/// </summary>
		/// <param name="TimerResolution">The number of milliseconds between timer updates.</param>
		public Timekeeper(int TimerResolution) {
			_StartTime = DateTime.Now;
			_Timers    = new SortedList<Timer>();
			_TimerResolution = TimerResolution;

			_UpdateRunning = true;
			_UpdateThread = new Thread(UpdateMain);
			_UpdateThread.Start();
		}

		/// <summary>
		/// Disposes of a Timekeeper.
		/// </summary>
		public void Dispose() {
			_UpdateRunning = false;
			_UpdateThread.Join();
		}

		/// <summary>
		/// Gets the date and time when this Timekeeper was started.
		/// </summary>
		public DateTime StartTime {
			get {
				return _StartTime;
			}
		}

		/// <summary>
		/// Gets the amount of time that this Timekeeper has been running.
		/// </summary>
		public TimeSpan RunningTime {
			get {
				return DateTime.Now - _StartTime;
			}
		}

		/// <summary>
		/// Gets a count of milliseconds of how long this Timekeeper has been running.
		/// </summary>
		public int TickCount {
			get {
				return RunningTime.Milliseconds;
			}
		}

		/// <summary>
		/// Gets the collection of Timers being tracked by the Timekeeper.
		/// </summary>
		public SortedList<Timer> Timers {
			get {
				return _Timers;
			}
		}
/*
		/// <summary>
		/// Adds a non-recurring timer.
		/// </summary>
		/// <param name="Owner">The module that owns the timer.</param>
		/// <param name="Tag">A tag that identifies the timer to the module.</param>
		/// <param name="ExpireTime">The time that the timer should expire.</param>
		public static void AddTimer(Module Owner, string Tag, DateTime ExpireTime) {
			AddTimer(Owner, Tag, ExpireTime, 0);
		}

		/// <summary>
		/// Adds a non-recurring timer.
		/// </summary>
		/// <param name="Owner">The module that owns the timer.</param>
		/// <param name="Tag">A tag that identifies the timer to the module.</param>
		/// <param name="ExpireTime">The first time that the timer should expire.</param>
		/// <param name="Interval">The number of milliseconds between expirations, after the first.</param>
		public static void AddTimer(Module Owner, string Tag, DateTime ExpireTime, int Interval) {
			Timer T = new Timer(ExpireTime, Interval, Owner, Tag);
			_Timers.Add(T);
		}

		/// <summary>
		/// Removes all timers owned by the given module.
		/// </summary>
		/// <param name="Owner">The module whose timers to remove.</param>
		public static void RemoveTimer(Module Owner) {
			// run through and remove any timers owned by the given module
			for (int i=0; i<_Timers.Count; ++i) {
				Timer T = _Timers[i] as Timer;
				if (T.Match(Owner)) {
					_Timers.RemoveAt(i);
					--i;
				}
			}
		}

		/// <summary>
		/// Removes a timer.
		/// </summary>
		/// <param name="Owner">The module that owns the timer.</param>
		/// <param name="Tag">The tag used by the module to identify the timer.</param>
		public static void RemoveTimer(Module Owner, string Tag) {
			// run through and remove any timers owned by the given module that have the specified tag
			for (int i=0; i<_Timers.Count; ++i) {
				Timer T = _Timers[i] as Timer;
				if (T.Match(Owner, Tag)) {
					_Timers.RemoveAt(i);
					--i;
				}
			}
		}
*/
		/// <summary>
		/// The main method for the Update thread.
		/// </summary>
		private void UpdateMain() {
			// keep going until we're told to stop
			while (_UpdateRunning) {
				Update();
				Thread.Sleep(_TimerResolution);
			}
		}

		/// <summary>
		/// Updates the timekeeper and sends any events that need to be sent by timers.
		/// </summary>
		private void Update() {
			// if we have no timers this is easy!
			if (_Timers.Count == 0) {
				return;
			}

			// keep going until the first timer isn't expiring this update
			bool Done = false;
			while (!Done) {
				// remove the first timer from the list
				// since the list is sorted, this will be the timer set to expire next
				Timer CurrentTimer = _Timers[0] as Timer;
				_Timers.RemoveAt(0);

				// check it, causing it to fire events if necessary
				int Expirations = CurrentTimer.Update();

				// if it's still alive, re-insert it into the sorted list
				if (CurrentTimer.IsAlive) {
					_Timers.Add(CurrentTimer);
				}

				// if the timer didn't expire at all, then no more timers could go off this update
				if (Expirations == 0) {
					Done = true;
				}

				// if there are no more timers then we're done
				if (_Timers.Count == 0) {
					Done = true;
				}
			}
		}
	}

	/// <summary>
	/// A single timer being tracked by the Timekeeper.
	/// </summary>
	internal class Timer : IComparable<Timer> {
		/// <summary>
		/// A predicate class which returns true for Timers that are owned by a given owner.
		/// </summary>
		public class OwnerMatches {
			// member data
			private IMessageHandler _Owner; // the owner to match against

			/// <summary>
			/// Creates a new OwnerMatches.
			/// </summary>
			/// <param name="Owner">The owner to match against.</param>
			public OwnerMatches(IMessageHandler Owner) {
				_Owner = Owner;
			}

			/// <summary>
			/// Checks if the given Timer's owner matches this instance's owner.
			/// </summary>
			/// <param name="T">The Timer whose owner to check.</param>
			/// <returns>True if the Timer is owned by this instance's owner, false otherwise.</returns>
			public virtual bool Check(Timer T) {
				return (_Owner == T.Owner);
			}
		}

		/// <summary>
		/// A predicate class which returns true for Timers that are owned by a given Module and have a given ID.
		/// </summary>
		public class IDMatches : OwnerMatches {
			// member data
			private int _ID; // the ID to match against

			/// <summary>
			/// Creates a new IDMatches.
			/// </summary>
			/// <param name="Owner">The owner to match against.</param>
			/// <param name="ID">The ID to match against.</param>
			public IDMatches(IMessageHandler Owner, int ID)
				: base(Owner) {
				_ID = ID;
			}

			/// <summary>
			/// Checks if the given Timer's owner matches this instance's Module and ID.
			/// </summary>
			/// <param name="T">The Timer whose owner and ID to check.</param>
			/// <returns>True if the Timer is owned by this instance's Module and has this instance's ID, false otherwise.</returns>
			public override bool Check(Timer T) {
				return (_ID == T.ID && base.Check(T));
			}
		}

		// member data
		private IMessageHandler _Owner;    // the owner of the timer
		private int             _ID;       // an ID unique to the owning module
		private DateTime        _Time;     // the time that the timer will go off next
		private int             _Count;    // the number of times that the timer will expire, or 0 to repeat forever
		private int             _Interval; // number of milliseconds between each firing

		/// <summary>
		/// Creates a new timer that fires once at the given time.
		/// </summary>
		/// <param name="Owner">The owner of the timer that will receive messages when it expires.</param>
		/// <param name="ID">An ID that's meaningful (and unique) to the owning module.</param>
		/// <param name="Time">The time that the timer should fire.</param>
		public Timer(IMessageHandler Owner, int ID, DateTime Time)
			: this(Owner, ID, Time, 1, 0) {
		}

		/// <summary>
		/// Creates a new timer that starts immediately and repeats indefinitely.
		/// </summary>
		/// <param name="Owner">The owner of the timer that will receive messages when it expires.</param>
		/// <param name="ID">An ID that's meaningful (and unique) to the owning module.</param>
		/// <param name="Interval">The number of milliseconds between the timer's repeating expirations.</param>
		public Timer(IMessageHandler Owner, int ID, int Interval)
			: this(Owner, ID, DateTime.Now, 0, Interval) {
		}

		/// <summary>
		/// Creates a new timer that starts immediately and repeats a given number of times.
		/// </summary>
		/// <param name="Owner">The owner of the timer that will receive messages when it expires.</param>
		/// <param name="ID">An ID that's meaningful (and unique) to the owning module.</param>
		/// <param name="ExpireCount">The number of times that the timer will expire, or 0 to repeat indefinitely.</param>
		/// <param name="Interval">The number of milliseconds between the timer's repeating expirations.</param>
		public Timer(IMessageHandler Owner, int ID, int ExpireCount, int Interval)
			: this(Owner, ID, DateTime.Now, ExpireCount, Interval) {
		}

		/// <summary>
		/// Creates a new timer that starts at the given time and repeats indefinitely.
		/// </summary>
		/// <param name="Owner">The owner of the timer that will receive messages when it expires.</param>
		/// <param name="ID">An ID that's meaningful (and unique) to the owning module.</param>
		/// <param name="Time">The time that the timer should first expire.</param>
		/// <param name="Interval">The number of milliseconds between the timer's repeating expirations.</param>
		public Timer(IMessageHandler Owner, int ID, DateTime Time, int Interval)
			: this(Owner, ID, Time, 0, Interval) {
		}

		/// <summary>
		/// Creates a new timer that starts at the given time and repeats a given number of times.
		/// </summary>
		/// <param name="Owner">The owner of the timer that will receive messages when it expires.</param>
		/// <param name="ID">An ID that's meaningful (and unique) to the owning module.</param>
		/// <param name="Time">The time that the timer should first expire.</param>
		/// <param name="ExpireCount">The number of times that the timer will expire, or 0 to repeat indefinitely.</param>
		/// <param name="Interval">The number of milliseconds between the timer's repeating expirations.</param>
		public Timer(IMessageHandler Owner, int ID, DateTime Time, int ExpireCount, int Interval) {
			_Owner    = Owner;
			_ID       = ID;
			_Time     = Time;
			_Interval = Interval;
			_Count    = ExpireCount;
		}

		/// <summary>
		/// Gets whether or not this timer is scheduled to expire again.
		/// </summary>
		public bool IsAlive {
			get {
				return (_Count >= 0);
			}
		}

		/// <summary>
		/// Gets the owner of the Timer.
		/// </summary>
		public IMessageHandler Owner {
			get {
				return _Owner;
			}
		}

		/// <summary>
		/// Gets the ID that uniquely identifies this Timer to its owner.
		/// </summary>
		public int ID {
			get {
				return _ID;
			}
		}

		/// <summary>
		/// Gets the next time that this Timer is set to expire.
		/// </summary>
		public DateTime NextExpiration {
			get {
				return _Time;
			}
		}

		/// <summary>
		/// Gets the number of milliseconds between expirations of this Timer.  0 means that this Timer does not repeatedly expire.
		/// </summary>
		public int Interval {
			get {
				return _Interval;
			}
		}

		/// <summary>
		/// Gets the number of times that this Timer will expire before stopping.
		/// </summary>
		public int ExpireCount {
			get {
				return _Count;
			}
		}

		/// <summary>
		/// Checks whether the timer needs to fire or not and reacts appropriately.
		/// </summary>
		/// <returns>The number of times that the timer fired.</returns>
		public int Update() {
			// if our expire count is negative then we're stopped
			if (_Count < 0) {
				return 0;
			}

			// keep recurring until the timer's time is in the future
			int Count = 0;
			while (DateTime.Now > _Time) {
				// send the owner a message
				_Owner.HandleMessage(new TimerExpiredMessage(_ID));

				// increment our count
				++Count;

				if (_Count == 1) {
					// that was our last expiration
					_Count = -1;
				} else if (_Count > 0) {
					// decrement our expire count
					--_Count;
				}

				// if we're still alive, step up our next fire time
				if (IsAlive) {
					_Time += TimeSpan.FromMilliseconds(_Interval);
				} else {
					break;
				}
			}

			// return the count
			return Count;
		}

		/// <summary>
		/// Compares this Timer to another one and sorts them based on their next expiration time.
		/// </summary>
		/// <param name="Other">The Timer to compare to this one.</param>
		/// <returns>&lt;0 if this&lt;Other, &gt;0 if this&gt;Other, 0 if this==Other</returns>
		public int CompareTo(Timer Other) {
			return _Time.CompareTo(Other._Time);
		}
	}
}
